﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Collections;

namespace QuadRemote.Panels
{
    class Graph : QuadPanel
    {
        protected int _freq;
        private float _scaleX;
        protected float _gain = 0.95F;
        protected int bufferSize;
        protected int bufferOffset;
        private PointF[] line;

        protected List<GraphData> graphs;
        protected float graphWidth;
        protected float graphHeight;
        private float maxYold;
        private float minYold;

        private Font fontMarks;
        private Pen whitePen;
        private Pen grayPen;
        private Brush whiteBrush;

        public Graph(String name, int frequency, float scaleX, Color color) : base(name, 200,100, color, frequency)
        {
            graphs = new List<GraphData>();
            _freq = frequency;
            _scaleX = scaleX;
            fontMarks = new Font("Calibri", 12);
            whitePen = Pens.White;
            grayPen = new Pen(Color.FromArgb(32, 255, 255, 255));
            whiteBrush = Brushes.White;
        }

        public void init()
        {
            graphWidth = PanelWidth - 30;
            graphHeight = PanelHeight - 30;

            bufferSize = (int)Math.Ceiling((double)graphWidth / _scaleX) + 2;
            bufferOffset = 0;
            line = new PointF[bufferSize - 2];

            foreach (GraphData data in graphs)
                data.data = new float[bufferSize];
        }

        public override void resized()
        {
            if (graphs == null) return;

            init();
        }

        private float[] getMinMax()
        {
            float minY = 0;
            float maxY = 0.0001F;

            foreach (GraphData data in graphs)
            {
                for (int i = 0; i < bufferSize - 1; i++)
                {
                    //if (data.data[i] == float.MaxValue) continue;
                    if (data.type == GraphType.DIFFERENTIAL ||
                        data.type == GraphType.DIFFERENTIAL_UPSCALED)
                    {
                        if (i > 0)
                        {
                            int pos = bufferOffset - i;
                            if (pos < 0) pos += bufferSize;
                            int prevPos = pos - 1;
                            if (prevPos < 0) prevPos += bufferSize;

                            float value = (data.data[pos] - data.data[prevPos]) * data.scale;

                            if (data.type == GraphType.DIFFERENTIAL_UPSCALED)
                                value *= _freq;

                            if (value > maxY)
                                maxY = value;
                            else if (value < minY)
                                minY = value;
                        }
                    }
                    else
                    {
                        float value = data.data[i];
                        value *= data.scale;

                        if (value > maxY)
                            maxY = value;
                        else if (value < minY)
                            minY = value;
                    }
                }
            }


            maxY *= 1.02F;
            minY *= 1.02F;

            maxY = (float)Math.Max(maxYold * _gain + maxY * (1 - _gain), maxY);
            minY = (float)Math.Min(minYold * _gain + minY * (1 - _gain), minY);

            if (minY > 0) minY = 0;
            if (maxY < 0) maxY = 0;
            if (maxY - minY < 0.0001) maxY += 0.0001F;


            maxYold = maxY;
            minYold = minY;

            return new float[] { minY, maxY };
        }


        float[] markValid = { 0.5f, 1, 5, 10, 30, 60, 300, 600, 3600 };
        int[] markValidDiv = { 1, 1, 1, 1, 1, 60, 60, 60, 3600 };
        String[] markValidValue = { "s", "s", "s", "s", "s", "m", "m", "m", "h" };

        float[] markValidVert = new float[] {0.0001f,0.001f, 0.01f, 0.05f, 0.1f, 0.25f, 0.5f, 1, 2.5f, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000,100000,1000000 };

        public override void PaintPanel(Graphics g)
        {
            float[] minmax = getMinMax();

            float minY = minmax[0];
            float maxY = minmax[1];

            float yScale = graphHeight / (maxY - minY);
            float yCenter = yScale * maxY;

            g.DrawLine(whitePen, 0, yCenter, graphWidth, yCenter);


            // Marks
            float mark = (graphWidth / (float)_scaleX) / (float)_freq;

            int markIndex = 0;
            for (int i = markValid.Length - 1; i >= 0; i--)
            {
                if (mark / 8.0 >= markValid[i])
                {
                    markIndex = i;
                    break;
                }
            }

            Font f = fontMarks;
            
            for (float i = 0; i <= Math.Round((float)((float)graphWidth / ((float)_freq * _scaleX))); i += markValid[markIndex])
            {
                int posX = (int)graphWidth - (int)(i * _freq * _scaleX);

                g.DrawLine(grayPen, posX, 0, posX, graphHeight);

                g.DrawLine(whitePen,posX, (int)yCenter, posX, (int)yCenter + 3);

                if (i % markValid[markIndex + 1] != 0)
                    continue;

                float m = ((float)i / (float)markValidDiv[markIndex]);

                if (m == 0) continue;
                String s = (Math.Ceiling(m) == Math.Floor(m) ? (int)m :
                        Math.Round(m * 10) / 10.0) + markValidValue[markIndex];
                
                g.DrawString(s, f, whiteBrush, posX, yCenter);
            }

            // y scale / mark calculations
            float range = Math.Abs(maxY - minY);
            mark = range / 5;

            
            float useMark = markValid[0];
            for (int i = 0; i < markValidVert.Length; i++)
            {
                if (mark > markValidVert[i])
                {
                    useMark = markValidVert[i];
                }
                else
                {
                    break;
                }
            }

            int skipLabel = (int)Math.Ceiling(25 / (useMark * yScale));

            // positive marks
            for (float y = yCenter, c = 0; y >= 0; y -= useMark * yScale)
            {
                String s;
                if (Math.Floor(useMark * c) == Math.Ceiling(useMark * c))
                    s = (int)(c * useMark) + "";
                else if (useMark >= 0.01F)
                    s = Math.Round(c * useMark * 100.0) / 100.0 + "";
                else if (useMark >= 0.001F)
                    s = Math.Round(c * useMark * 1000.0) / 1000.0 + "";
                else
                    s = Math.Round(c * useMark * 10000.0) / 10000.0 + "";

                g.DrawLine(whitePen,graphWidth, y, graphWidth + 3, y);
                g.DrawLine(grayPen, 0, y, graphWidth, y);
                if (c % skipLabel == 0)
                {
                    g.DrawString(s, f, whiteBrush,graphWidth + 5, y);
                    g.DrawLine(whitePen,graphWidth + 3, y, graphWidth + 6, y);
                }
                c++;
            }
            // negative marks
            for (float y = yCenter, c = 0; y <= graphHeight; y += useMark * yScale)
            {
                String s;
                if (Math.Floor(useMark * c) == Math.Ceiling(useMark * c))
                    s = (int)(-c * useMark) + "";
                else
                    s = (-c * useMark) + "";

                g.DrawLine(whitePen,graphWidth, y, graphWidth + 3, y);
                g.DrawLine(grayPen, 0, y, graphWidth, y);

                if (c % skipLabel == 0)
                {
                    g.DrawString(s, f, whiteBrush, graphWidth + 5, y);
                    g.DrawLine(whitePen, graphWidth + 3, y, graphWidth + 6, y);
                }
                c++;
            }

            // vertical axis
            g.DrawLine(whitePen,graphWidth, 0, graphWidth, graphHeight);

            float dataX = 10;

            foreach (GraphData data in graphs)
            {
                Pen p = data.pen;

                float displayValue = 0;
                float latestPos = -100000;

                for (int i = 0; i < bufferSize - 2; i++)
                {
                    int pos = bufferOffset - i - 1;
                    if (pos < 0) pos += bufferSize;

                    float value;

                    if (data.type == GraphType.DIFFERENTIAL ||
                        data.type == GraphType.DIFFERENTIAL_UPSCALED)
                    {
                        int prevPos = pos - 1;
                        if (prevPos < 0) prevPos += bufferSize;
                        if (data.data[pos] == float.MaxValue)
                            value = 0;
                        else
                            value = (data.data[pos] - data.data[prevPos]) * data.scale;

                        if (data.type == GraphType.DIFFERENTIAL_UPSCALED)
                            value *= _freq;
                    }
                    else
                    {
                        if (data.data[pos] == float.MaxValue)
                            value = 0;
                        else
                            value = (data.data[pos] * data.scale);
                    }

                    if (latestPos == -100000 && data.data[pos] != float.MaxValue) // todo: fugly! invalid crap
                    {
                        latestPos = data.data[pos];
                        if (data.type == GraphType.DIFFERENTIAL || data.type == GraphType.DIFFERENTIAL_UPSCALED)
                        {
                            int prevPos = pos - 1;
                            if (prevPos < 0) prevPos += bufferSize;
                            latestPos -= data.data[prevPos];
                        }

                        if (data.display == GraphDisplay.REALTIME)
                            displayValue = latestPos;
                    }

                    if (data.display == GraphDisplay.AVG_PER_SEC)
                    {
                        if (i < _freq)
                        {
                            displayValue += value;
                        }
                    }

                    float posY = yCenter - yScale * value;

                    line[i].X = graphWidth - (((float)i * _scaleX));
                    line[i].Y = (posY);
                }

                g.DrawLines(p,line);

                g.DrawString(data.name + "=" + data.data[bufferOffset],f,whiteBrush,dataX,graphHeight + 10);
                dataX += 150;
            }
        }
    }
}
